using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Threading;
using System.Net.Sockets;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace Servidor
{
	
	public class Server
	{
		/*
		 * Listado de clientes conectados 
		 * identificados por su id
		 * <Guid,Socket>
		 */
		private Hashtable clientsTable      = new Hashtable();
		/*
		 * Socket de escucha del servidor
		 */
		private Socket listener;
		/*
		 * Hilo de escucha
		 */
    	private Thread listenThread;
		/*
		 * Numero maximo de clientes permitidos
		 */
		private int maxClients = 100;
		
		
		
		
		
		/*
		 * Constructor
		 */
		public Server ()
		{
			this.listener     = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
			listener.Bind(new IPEndPoint(IPAddress.Any,5050));
			listener.Listen(100);			
      		this.listenThread = new Thread(new ThreadStart(StartListening));
      		this.listenThread.Start(); //Se inicia el hilo de escucha
			Console.WriteLine("Esperando Conecciones...");
			LogMessageToFile("Success!: ", "Esperando Conecciones");
            
		}//Server
		
		/*
		 * Procedimiento de escuchar por clentes
		 */
		public void StartListening(){
			//Ciclo infinito para escuchar
			while(true){
				Socket client = listener.Accept();
				Thread clientThread = new Thread(new ParameterizedThreadStart(handleClient));
				clientThread.Start(client);//si hay un cliente inicia un thread para su manejo
			}
		}//StartListening
		
		/*
		 * detiene la escucha
		 * Cerrando a todos los clientes activos
		 */
		public void StopListening(){
			foreach(DictionaryEntry socket in clientsTable){
				Socket s = (Socket)socket.Value;
				s.Disconnect(true);
				s.Close();			
				s.Shutdown(SocketShutdown.Both);
			}
			listener.Disconnect(true);
			listener.Shutdown(SocketShutdown.Both);
			listener.Close();
			listenThread.Abort();
		}//StopListening
		
		/*
		 * Proceso que maneja a cada cliente
		 * Recibe un objeto que identifica al socket del cliente
		 */
		private void handleClient(object socket){
			Socket client = (Socket)socket;
			Console.Write("Cliente conectado...{0}",client.RemoteEndPoint);
			LogMessageToFile("Success!:","Cliente conectado");
			Guid id = addClient(client); //Envia mensaje de bienvenida
			//comienza comunicacion
			client.Blocking = true;
			if(client.Connected){
				//espero recibir mensaje
				byte[] r = Recibir(client);
				//creararchivo
				crearArchivo(r,id);
				handleData(id,client); //maneja la data recibida leyendo desde el archivo
				r = Recibir(client);
				crearArchivo(r,id);
				handleData(id,client);
				if(!client.Connected){
					clientsTable.Remove(id);
					client.Shutdown(SocketShutdown.Both);
					client.Close();
					maxClients++;
					cleanFiles(id);
				}
			}else{
				clientsTable.Remove(id);
				client.Shutdown(SocketShutdown.Both);
				client.Close();
				if(File.Exists(Directory.GetCurrentDirectory()+"/xml/serialize"+id+".xml"))
					File.Delete(Directory.GetCurrentDirectory()+"/xml/serialize"+id+".xml");
				if(File.Exists(Directory.GetCurrentDirectory()+"/xml/recibido"+id+".xml"))
					File.Delete(Directory.GetCurrentDirectory()+"/xml/recibido"+id+".xml");
				maxClients++;
			}
		}//handleClient
		
		
		/*
		 * Metodo para agregar un nuevo cliente al listado
		 * Le envia un mensaje al cliente (Xml) avisando de que fue aceptada 
		 * su conección
		 * @param Socket socket del cliente
		 * @return Guid id del cliente agregado
		 */
		private Guid addClient(Socket socket){
			Guid id = Guid.NewGuid();
			if(maxClients>0){				
				clientsTable.Add(id,socket); //añade el cliente
				Console.WriteLine("\t id cliente = {0}",id);
				LogMessageToFile("Success!:","Cliente "+id);
				//se crea un nuevo objeto Mensaje
				Mensaje m = new Mensaje(id);
				m.tipo      = Mensaje.MsgType.MSG;
				m.contenido = "Bienvenido al servidor";
				m.clientID  = id;
				maxClients--;
				SendMsg(id,m); //Envia el mensaje al cliente conectado
			}else{
				Mensaje m = new Mensaje(id);
				m.tipo      = Mensaje.MsgType.MSG;
				m.contenido = "El servidor esta ocupado. Intente mas tarde";
				m.clientID  = id;
		
				maxClients--;
				SendMsg(id,m);
				socket.Disconnect(true);
				socket.Shutdown(SocketShutdown.Both);
				LogMessageToFile("Error:! ","Server too busy cliente "+id);
			}
			return id;
		}//addClient
		
		
		/*
		 * Metodo para crear un archivo XML que representa
		 * la data recibida desde un cliente
		 * el archivo se identifica por el id de cliente
		 * @param byte[] data recibida
		 * @param Guid id del cliente
		 */
		private void crearArchivo(byte[] recibido, Guid id){
			string archivo = Directory.GetCurrentDirectory()+"/xml/recibido"+id.ToString()+".xml";
			if(File.Exists(archivo)){
				File.Delete(archivo);
			}//if
			try{
				Stream st       = new MemoryStream(recibido);
				Mensaje msg     = Mensaje.Deserialize(st);
				st.Close();		
				Mensaje m = new Mensaje(id);
				m.tipo     = msg.tipo;
				m.contenido = msg.contenido;
				m.clientID = id;
				m.RSerialize();
			}catch(Exception e){
				Console.WriteLine("Error crear : "+e.Message);
				LogMessageToFile("Error!: ",e.Message);
			}
		}//crearArchivo
		
		/*
		 * Metodo de manejo de la data recibida
		 * Revisa lo recibido y decide que responder
		 * @param Guid id del cliente
		 * @param Socket del cliente
		 */
		private void handleData(Guid id, Socket handler){
			String archivo  = Directory.GetCurrentDirectory()+"/xml/recibido"+id.ToString()+".xml";
			Mensaje mensaje = Mensaje.Deserialize(archivo,id);
			Guid idr         = mensaje.clientID;
			Mensaje.MsgType tipo_mensaje = mensaje.tipo;
			
			if(tipo_mensaje == Mensaje.MsgType.VIDEO){
				int videoID = Int32.Parse(mensaje.contenido);
				Console.WriteLine("Intentando enviar video");
				SendVideo(idr,videoID);
			}else if(tipo_mensaje == Mensaje.MsgType.LIST){
				SendVideoList(idr);
			}else{
				Mensaje m  = new Mensaje(idr);
				m.clientID = idr;
				m.contenido= "NOOP";
				m.tipo     = Mensaje.MsgType.MSG;
				SendMsg(idr,m);
			}//if(videoID!=0)			
		}//handleData
		
		/*
		 * Busca dentro de la tabla de clientes
		 * el id del cliente que corresponde al
		 * al parametro Socket
		 * @param Socket socket del cliente
		 * @return Guid id del cliente
		 */
		private Guid findKey(Socket socket){
			Guid kid = new Guid();
			foreach(Guid id in clientsTable.Keys){
				if(clientsTable[id] == socket){
					kid = id;
					break;
				}//if
			}//foreach
			return kid;
		}//findKey
		
		
		/*
		 * Metodo para recibir informacion del cliente
		 * @param Socket del cliente
		 * @return byte[] data recibida
		 */
		private  byte[] Recibir(Socket s){
			
			int total = 0;
            int recv;
			int b = IPAddress.NetworkToHostOrder(4);
            byte[] datasize = new byte[b];
            if(s.Connected){
			    recv = s.Receive(datasize, 0, 4, 0);//recibe el tamaño de lo que se recibira
				int size = BitConverter.ToInt32(datasize, 0);
				
				int dataleft = IPAddress.NetworkToHostOrder(size);
                byte[] data = new byte[IPAddress.NetworkToHostOrder(size)];
				size = dataleft;
				while (total < size){
					try
                    {
						recv = s.Receive(data, total, dataleft, 0);
						if (recv == 0)
                        {
                            break;
                        }//if
                        total += recv;
                        dataleft -= recv;
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Error:" + e.Message);
						LogMessageToFile("Error!: ",e.Message);
						Guid id = findKey(s);
						cleanFiles(id);
                        break;
                    }
                }//while
                return data;
            }else{
				Guid id = findKey(s);
				cleanFiles(id);
                throw new Exception("Cliente Desconectado");
            }			
        }//Recibir
		
		
		/*
		 * Metodo para el envio de la lista de videos
		 * @param Socket del cliente
		 * @param Guid id del cliente
		 */
		private void SendVideoList(Guid id){
			List<VideoList> listado = BD.List(id);
			String archivo = SerializeList(listado,id);
			FileStream fs = new FileStream(archivo, FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
			Send(id,fs);
		}//SendVideoList
		
		/*
		 * Metodo para enviar el video al cliente
		 * @param Guid id del cliente
		 * @param int id del video
		 */
		private void SendVideo(Guid id,int videoID){
			string videoPath = BD.getVideoPath(videoID);
			FileStream fs = new FileStream(videoPath,FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
			LogMessageToFile("Success!: ","Recibida solicitud de video");
			
			try{
				Send(id,fs);
				fs.Close();
			}catch(Exception e){
				Mensaje m  = new Mensaje(id);
				m.clientID = id;
				m.contenido= e.ToString();
				m.tipo	   = Mensaje.MsgType.ERROR;
				SendMsg(id,m); 
				LogMessageToFile("Error:! ",e.Message);
			}
				
				
		}//SendVideo
		
		/*
		 * Metodo para enviar un objeto Mensaje 
		 * al cliente (el archivo XML que representa el Mensaje)
		 * @param Socket socket del cliente
		 * @param Mensaje objeto mensaje a enviar
		 */
		private void SendMsg(Guid id, Mensaje mensaje){
			mensaje.Serialize();
			FileStream fs = new FileStream(mensaje.getFile(),FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
			Send(mensaje.clientID,fs);
		}//SendMsg
		
		
		/*
		 * Metodo para el envio por medio del socket
		 * @param Socket socket del cliente
		 * @param FileStream para lectura y envio
		 */
		private void Send(Guid id, FileStream fs){
			Socket socket = (Socket)clientsTable[id]; //obtiene el socket del cliente			
			if(socket.Connected){
				byte[] data = ReadFully(fs);
				int total = 0;
				int size = data.Length;				
				int dataleft = size;
				int sent;
				byte[] datasize = new byte[4];
				datasize        = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(size));
                try
                {
                    sent = socket.Send(datasize); //envia tamaño del archivo
                    while (total < size) //Comienza el envio
                    {
                        try
                        {
                            sent = socket.Send(data, total, dataleft, SocketFlags.None);
                            total += sent;
                            dataleft -= sent;
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
							LogMessageToFile("Error:! ",e.Message);
							cleanFiles(id);
                            break;
                        }
                    }//while
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
					LogMessageToFile("Error:! ",e.Message);
					cleanFiles(id);
                }
			}else{
				Console.WriteLine("Error");
				LogMessageToFile("Error:! ","Cliente "+id+" desconectado");
				cleanFiles(id);
			}
		}//Send
		
		
		/*
		 * Metodo de lectura de un archivo
		 * @param Stream stream de lectura
		 * @return byte[] data leida
		 */
		private byte[] ReadFully (Stream stream){    		
			int initialLength = 1024;
    		byte[] buffer = new byte[initialLength];
    		int read=0;
    		int chunk;
    		while ( (chunk = stream.Read(buffer, read, buffer.Length-read)) > 0){
        		read += chunk;
        		if (read == buffer.Length){
            		int nextByte = stream.ReadByte();
        		    if (nextByte==-1){
                		return buffer;
            		}//if
                    byte[] newBuffer = new byte[buffer.Length*2];
            		Array.Copy(buffer, newBuffer, buffer.Length);
            		newBuffer[read]=(byte)nextByte;
            		buffer = newBuffer;
            		read++;
        		}//if(read == buffer.Length)
    		}//while
    		byte[] ret = new byte[read];
    		Array.Copy(buffer, ret, read);
    		return ret;
		}//ReadFully	
		
		/* Metodo para la serializacion del listado de videos*/
		private String SerializeList(List<VideoList> listado, Guid id_cliente){
			XmlSerializer serializer = new XmlSerializer(typeof(List<VideoList>));
			FileStream f = new FileStream(Directory.GetCurrentDirectory()+"/xml/listado"+id_cliente+".xml",FileMode.Create,FileAccess.Write,FileShare.ReadWrite);
			TextWriter 	  tw = new StreamWriter(f);
			serializer.Serialize(tw,listado);
			tw.Close();
			return Directory.GetCurrentDirectory()+"/xml/listado"+id_cliente+".xml";
		}//SerializeList
		
		/* Metodo de limpieza de archivos en el directorio /xml/ */
		private void cleanFiles(Guid idcliente){
			string dir = Directory.GetCurrentDirectory()+"/xml/";
			foreach(string f in Directory.GetFiles(dir,"*"+idcliente+".xml")){
				File.Delete(f);	
			}
		}//cleanFiles
		
		/**LOGGING*/
		
		private string GetTempPath(){
    		string path =Directory.GetCurrentDirectory()+"/";// System.Environment.GetEnvironmentVariable("TEMP");
    		//if (!path.EndsWith("\\")) path += "\\";
    		return path;
		}

		private void LogMessageToFile(string tag, string msg){
    		System.IO.StreamWriter sw = System.IO.File.AppendText(
        	GetTempPath() + "serverLog.log");
    		try
    		{
        		string logLine = System.String.Format(
            		"{0:G}: {1} - {2}.", System.DateTime.Now, tag,msg);
        		sw.WriteLine(logLine);
    		}
    		finally
    		{
        		sw.Close();
    		}
		}
		
	}//class	
}//namespace

